﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace Trentacular.SharePoint.WSPUtil
{
    public static class SolutionExtensions
    {
        public static IList<WSPFeatureDefinition> ExtractFeatureDefinitions(this SPSolution spSolution)
        {
            using (var parser = new SolutionManifestParser(spSolution))
            {
                return parser.Parse();
            }
        }
    }

    public class SolutionManifestParser : IDisposable
    {
        public const string SPNamespaceUri = "http://schemas.microsoft.com/sharepoint/";

        public SPSolution SPSolution { get; private set; }
        public string TempPath { get; private set; }
        public string TempFile { get; private set; }

        public SolutionManifestParser(SPSolution spSolution)
        {
            SPSolution = spSolution;

            var tempId = Guid.NewGuid().ToString();
            var workPath = Path.GetTempPath();
            TempPath = Path.Combine(workPath, tempId);
            TempFile = Path.Combine(workPath, tempId) + ".wsp";
        }

        public IList<WSPFeatureDefinition> Parse()
        {
            SPSolution.SolutionFile.SaveAs(TempFile);

            Directory.CreateDirectory(TempPath);
            var container = new CabinetContainer(TempFile, TempPath);
            container.ExtractAll();

            var manifestFile = Path.Combine(TempPath, "manifest.xml");

            var document = new XPathDocument(manifestFile);
            var navigator = document.CreateNavigator();

            return ParseSolutionManifest(navigator);
        }

        private IList<WSPFeatureDefinition> ParseSolutionManifest(XPathNavigator navigator)
        {
            var manager = new XmlNamespaceManager(navigator.NameTable);
            manager.AddNamespace("sp", SPNamespaceUri);

            var solutionNavigator = navigator.SelectSingleNode("/sp:Solution", manager);
            var featureManifests = solutionNavigator.Select("sp:FeatureManifests/sp:FeatureManifest", manager);

            // Initialize the features collection
            var results = new List<WSPFeatureDefinition>(featureManifests.Count);

            while (featureManifests.MoveNext())
            {
                var location = featureManifests.Current.GetAttribute("Location", string.Empty);
                results.Add(ParseFeatureManifest(location));
            }

            return results;
        }

        private WSPFeatureDefinition ParseFeatureManifest(string location)
        {
            var manifestFile = Path.Combine(TempPath, location);

            var document = new XPathDocument(manifestFile);
            var navigator = document.CreateNavigator();
            
            var manager = new XmlNamespaceManager(navigator.NameTable);
            manager.AddNamespace("sp", SPNamespaceUri);

            var featureNavigator = navigator.SelectSingleNode("/sp:Feature", manager);

            var activationDependenciesNavigator = featureNavigator.Select("sp:ActivationDependencies/sp:ActivationDependency", manager);

            var activationDependencies = new List<Guid>(activationDependenciesNavigator.Count);
            while (activationDependenciesNavigator.MoveNext())
            {
                var dependentFeatureId = activationDependenciesNavigator.Current.GetAttribute("FeatureId", string.Empty);
                activationDependencies.Add(new Guid(dependentFeatureId));
            }

            return new WSPFeatureDefinition
            {
                Name = location.Remove(location.Length - 12),
                Id = new Guid(featureNavigator.GetAttribute("Id", string.Empty)),
                Title = featureNavigator.GetAttribute("Title", string.Empty),
                Scope = GetScope(featureNavigator.GetAttribute("Scope", string.Empty)),
                Description = featureNavigator.GetAttribute("Description", string.Empty),
                ActivationDependencies = activationDependencies
            };
        }

        private SPFeatureScope GetScope(string scope)
        {
            return (SPFeatureScope)Enum.Parse(typeof(SPFeatureScope), scope, true);
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (Directory.Exists(TempPath))
                Directory.Delete(TempPath, true);

            if (File.Exists(TempFile))
                File.Delete(TempFile);
        }

        #endregion
    }
}
